package phase1.part2;

import java.io.File;
import java.io.FileNotFoundException;

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;

/**
 * An NFAFileReader extracts NFA Table Entries and other
 * necessary information from a .txt file.
 * 
 * @author Devin Wang
 * @version Nov 13, 2012
 */
public class NFAFileReader {
	private static final String STATES = "STATES";
	private static final String START = "START";
	private static final String ACCEPTING = "ACCEPT";

    private Map<String, State> states;
	private State start;
    private Set<Character> alphabet;

    /**
     * Create an NFAFileReader.
     * @param filename The input filepath
     * @throws FileNotFoundException
     * @throws FileFormatException
     */
	public NFAFileReader(String filename) throws FileNotFoundException, FileFormatException {
        states = new HashMap<>();
        alphabet = new TreeSet<Character>();

		Scanner scanner = new Scanner(new File(filename));
		while (scanner.hasNext()) {
			String line = scanner.nextLine();
			if (line.isEmpty()) {
				continue;
			}
			if (line.indexOf(STATES) == 0) {
				processStates(line);
			} else if (line.indexOf(START) == 0) {
				processStart(line);
			} else if (line.indexOf(ACCEPTING) == 0) {
				processAccepting(line);
			} else {
				processTransition(line);
			}
		}
	}

	/**
	 * Pull state labels from STATES line.
	 * @param line The STATES line
	 * @throws FileFormatException
	 */
	private final void processStates(String line) throws FileFormatException {
		String[] parts = postSpace(line).split(",");
		for (String label : parts) {
			states.put(label, new State(label));
		}
	}

	/**
	 * Pull starting state from start START line.
	 * @param line The START line
	 * @throws FileFormatException
	 */
	private final void processStart(String line) throws FileFormatException {
		String label = postSpace(line);
		start = states.get(label);
	}

	/**
	 * Pull accepting states from ACCEPT lines, set states to accepting and
	 * set their token classes.
	 * @param line An ACCEPT line
	 * @throws FileFormatException
	 */
	private final void processAccepting(String line) throws FileFormatException {
		String[] parts = line.split(" ");
		String tokenClass = parts[1];
		String[] stateLabels = parts[2].split(",");

		for (String label : stateLabels) {
			State s = states.get(label);
			s.setAccepting(true);
			s.setTokenClass(tokenClass);
		}
	}

	/**
	 * Pull transitions from transition lines, distinguish e-Transitions,
	 * add transitions to states, determine alphabet.
	 * @param line A transition line
	 */
	private final void processTransition(String line) {
		String[] parts = line.split(",");

		String from = parts[0];
        if(parts.length > 2){
            String pattern = parts[1];
            char[] chars = pattern.toCharArray();
            for (int i = 0; i < chars.length; i++) {
                if (chars[i] == '-' && i > 0 && i < chars.length - 1) {
                    for (char c = chars[i - 1]; c < chars[i + 1]; c++) {
                        alphabet.add(c);
                    }
                } else {
                    alphabet.add(chars[i]);
                }
            }
            String to = parts[2];
            Pattern p = new Pattern(pattern);
            Transition transition = new Transition(states.get(to),p);
            states.get(from).addTransition(transition);
        }
        else{
            String to = parts[1];
            Transition transition = new Transition(states.get(to),null);
            states.get(from).addTransition(transition);
        }
	}

	/**
	 * Get the rest of a line after a space.
	 * @param line A line with a space
	 * @return The rest of the line after a space.
	 * @throws FileFormatException
	 */
	private String postSpace(String line) throws FileFormatException {
		int space = line.indexOf(' ');

		if (space == -1) {
			throw new FileFormatException();
		}

		String postSpace = line.substring(space + 1);
		return postSpace;
	}

	/**
	 * Get start state.
	 * @return start state
	 */
	public State getStart() {
		return start;
	}
    
	/**
	 * Get alphabet.
	 * @return alphabet
	 */
    public Set<Character> getAlphabet() {
        return alphabet;
    }

	public static class FileFormatException extends Exception {
		private static final long serialVersionUID = 8448704966112475242L;

		public FileFormatException() {
		}

		public FileFormatException(String message, Throwable cause,
				boolean enableSuppression, boolean writableStackTrace) {
			super(message, cause, enableSuppression, writableStackTrace);
		}

		public FileFormatException(String message, Throwable cause) {
			super(message, cause);
		}

		public FileFormatException(String message) {
			super(message);
		}

		public FileFormatException(Throwable cause) {
			super(cause);
		}
	}
}